#include<stack>
#include<sstream>
#include "symmetricpolynomial.h"
#include "polynomial.h"

//This class is used in the stack to support a field type and a polynomial one
//It should be defined inside the function block
template<typename R>
class element{
    public:
        //Constructor
        element(void* elem, std::string elem_type):element_pointer(elem),element_type(elem_type){}
        std::string get_element_type(){return element_type;}
        void* get_element_pointer(){return element_pointer;}
        bool operator==(element& right){
            if(element_type!=right.element_type) return false;
            if(element_type=="integer"){
                return (*(int*)element_pointer==*(int*)right.element_pointer);
            }
            else if(element_type=="symmetricpolynomial"){
                return (*(symmetricpolynomial<R>*)element_pointer==*(symmetricpolynomial<R>*)right.element_pointer);
            }
            else if(element_type=="polynomial"){
                return (*(polynomial<R>*)element_pointer==*(polynomial<R>*)right.element_pointer);
            }
        }
        ~element(){
            if(element_type=="integer"){
                delete (int*)element_pointer;
            }
            else if(element_type=="symmetricpolynomial"){
                delete (symmetricpolynomial<R>*)element_pointer;
            }
            else if(element_type=="polynomial"){
                delete (polynomial<R>*)element_pointer;
            }
        }
        element& operator=(element& right){
            //Delete the value of element_pointer in case there is an element attributted
            if(element_pointer!=0){
                if(element_type=="integer"){
                    delete (int*)element_pointer;
                }
                else if(element_type="symmetricpolynomial"){
                    delete (symmetricpolynomial<R>*)element_pointer;
                }
                else if(element_type="polynomial"){
                    delete (polynomial<R>*)element_pointer;
                }
            }
            //Resetting the element
            element_type=right.element_type;
            if(right.element_type=="integer"){
                element_pointer=new int(*(int*)right.element_pointer);
            }
            else if(right.element_type=="symmetricpolynomial"){
                element_pointer=new symmetricpolynomial<R>(*(symmetricpolynomial<R>*)right.element_pointer);
            }
            else if(right.element_type=="polynomial"){
                element_pointer=new polynomial<R>(*(polynomial<R>*)right.element_pointer);
            }
            return *this;
        }
        //Copy constructor
        element(const element& right):element_type(right.element_type){
            if(right.element_type=="integer"){
                element_pointer=new int(*(int*)right.element_pointer);
            }
            else if(right.element_type=="symmetricpolynomial"){
                element_pointer=new symmetricpolynomial<R>(*(symmetricpolynomial<R>*)right.element_pointer);
            }
            else if(right.element_type=="polynomial"){
                element_pointer=new polynomial<R>(*(polynomial<R>*)right.element_pointer);
            }
        }
    private:
        std::string element_type;
        void* element_pointer;
};

template<typename T>
polynomial<T> elementary_symmetric_decomposition(symmetricpolynomial<T>& sp){
    T un(1);
    T zr(0);
    polynomial<T> res;
    std::stack<element<T> > stc;
    int est = 0;  //To store the state of the state machine that constitutes the algorithm
    symmetricpolynomial<T> p(sp);
    std::vector<int> deg;
    //The begining of the state machine
    while(est!=-1){  //int ff; std::cin>>ff;
        if(est==0){  std::cout<<"State 0"<<std::endl;
            element<T> tmp(new int(0),"integer");
            stc.push(tmp);
            std::cout<<"\tThe value of p is "<<p<<" in state 0"<<std::endl;
            element<T> tmp1(new symmetricpolynomial<T>(p),"symmetricpolynomial");
            stc.push(tmp1);
            est=1;  std::cout<<"Leaving state 0 to state 1"<<std::endl;
        }
        else if(est==1){  std::cout<<"State 1"<<std::endl;
            p=*(symmetricpolynomial<T>*)stc.top().get_element_pointer();
            std::cout<<"The value of p in state 1 is "<<p<<std::endl;
            stc.pop();
            std::cout<<"Testing the p degree..."<<std::endl;
            if(p.degree()==0){
                              std::cout<<"\tThe degree of p is 0 in state 1"<<std::endl;
                polynomial<T> rp(p.get_vars(),p.lcoeff());
                res = rp;  std::cout<<"\t\tres="<<res<<" when p.degree=0 in state 1"<<std::endl;
                est=2;  //Exit in this point with the answer
            }
            else if(p.is_empty()){
                 std::cout<<"\tp is null in state 1"<<std::endl;
                polynomial<T> zp;
                res = zp;
                est=2;
            }
            else{
                 std::cout<<"\tp is not null and have degree greater than 0"<<std::endl;
                deg=p.ldegree();
                T temp(p.lcoeff());
                int j=0;
                for(int i=0;i<deg.size();++i){
                    if(deg[i]>0){
                        deg[i]=deg[i]-1;
                        ++j;
                    }
                }
                std::vector<int> temp_s;
                for(int k=0;k<j;++k){
                    temp_s.push_back(1);
                }
                for(int k=j;k<sp.get_vars().size();++k){
                    typename std::vector<int>::iterator it = temp_s.begin();
                    temp_s.insert(it,0);
                }
                //aux1=symmetricpolynomial<T>(temp_s,sp.get_vars(),un);  //This is sigma[]
                stc.push(element<T>(new symmetricpolynomial<T>(p),"symmetricpolynomial"));
                stc.push(element<T>(new symmetricpolynomial<T>(temp_s,sp.get_vars(),un),"symmetricpolynomial"));
                stc.push(element<T>(new int(j),"integer"));  //Indicates which sigma it is being utilized
                stc.push(element<T>(new symmetricpolynomial<T>(deg,sp.get_vars(),un),"symmetricpolynomial"));
                stc.push(element<T>(new int(1),"integer"));  //Indicates the cicle
                stc.push(element<T>(new symmetricpolynomial<T>(deg,sp.get_vars(),temp),"symmetricpolynomial"));
            }
        }
        else if(est==2){   std::cout<<"State 2"<<std::endl;
            int count=*(int*)stc.top().get_element_pointer();
            stc.pop();
            if(count==0) est=-1;
            else if(count==1){
                symmetricpolynomial<T> lp = *(symmetricpolynomial<T>*)stc.top().get_element_pointer();
                stc.pop();
                int temp = *(int*)stc.top().get_element_pointer();
                stc.pop();
                symmetricpolynomial<T> aux = *(symmetricpolynomial<T>*)stc.top().get_element_pointer();
                stc.pop();
                p = *(symmetricpolynomial<T>*)stc.top().get_element_pointer();
                stc.pop();
                std::vector<std::string> v_temp;
                //Concatenate a string with an integer
                std::stringstream ss;
                std::string str_t_1="s[";
                std::string str_t_2="]";
                ss<<str_t_1<<temp<<str_t_2;
                std::string str(ss.str());
                v_temp.push_back(str);
                std::vector<int> al_temp;
                al_temp.push_back(1);
                polynomial<T> p_temp(v_temp,al_temp,un);
                stc.push(element<T>(new polynomial<T>(res.multiply(p_temp)),"polynomial"));
                stc.push(element<T>(new int(2),"integer"));
                T tt = p.lcoeff();
                symmetricpolynomial<T> aux1 = aux.multiply_constant(tt);
                symmetricpolynomial<T> aux2 = aux.multiply(lp);
                symmetricpolynomial<T> diff=p-aux2;
                std::cout<<"\t"<<p<<"-"<<aux2<<"="<<diff<<" at state 2"<<std::endl;
                stc.push(element<T>(new symmetricpolynomial<T>(diff),"symmetricpolynomial"));
                est=1;
            }
            else{
                polynomial<T> aux1 = *(polynomial<T>*)stc.top().get_element_pointer();
                stc.pop();
                polynomial<T> pt = res.add(aux1);
                std::cout<<"\tres="<<res<<", aux1="<<aux1<<" at state 2"<<std::endl;
                res = pt;
                est=2;
            }
        }
        else ;  //Nothing to do
    }
    return res;
}
